<?php

require_once('LogLevel.class.php');

#private static function log() { if(self::$__log === NULL) self::$__log = Log::getInstance(); return self::$__log; } private static $__log;

class Log {
	
	private static $instance = NULL;
	
	/** file handle */
	private $fh;
	
	/**
	 * Current set log level.
	 *
	 * @var LogLevel
	 */
	private $logLevel;
	
	/**
	 * Ring buffer for additional exception output.
	 *
	 * @var RingBuffer
	 */
	private $ringBuffer;
	
	private function Log() {
		$this->ringBuffer = new RingBuffer();
	}
	
	/**
	 * Get single instance.
	 *
	 * @return Log
	 */
	public static function instance() {
		if(self::$instance === NULL) {
			LogLevel::initLevels();
			self::$instance = new self;
		}
		return self::$instance;
	}
	
	public function open($path, LogLevel $logLevel) {
		if($this->fh !== NULL) {
			throw new Exception("Illegal state: Log already opened!");
		}
		$this->fh = fopen($path, 'a');
		if($this->fh === false) {
			throw new Exception('Could not open logfile: ' . $path);
		}
		$this->logLevel = $logLevel;
	}
	
	public function trace($msg, Exception $exception=NULL) {
		$this->writeFile(LogLevel::$TRACE, $msg, $exception);
	}
	
	public function debug($msg, Exception $exception=NULL) {
		$this->writeFile(LogLevel::$DEBUG, $msg, $exception);
	}
	
	public function info($msg, Exception $exception=NULL) {
		$this->writeFile(LogLevel::$INFO, $msg, $exception);
	}
	
	public function warn($msg, Exception $exception=NULL) {
		$this->writeFile(LogLevel::$WARN, $msg, $exception);
	}
	
	public function error($msg, Exception $exception=NULL) {
		$this->writeFile(LogLevel::$ERROR, $msg, $exception);
	}
	
	public function fatal($msg, Exception $exception=NULL) {
		$this->writeFile(LogLevel::$FATAL, $msg, $exception);
	}
	
	function writeFile(LogLevel $lvl, $msg, Exception $exception=null, LogLocation $location=NULL) {
		if($this->fh === NULL) {
			throw new Exception("Illegal state: Log not yet opened!");
		}
		
		$isRingBuffer = $location !== NULL;
		if(!$isRingBuffer) {
			$location = new LogLocation();
		} // else ... $location already set
		
		if($isRingBuffer === false && $lvl->isBelow($this->logLevel)) {
			$this->ringBuffer->add($lvl, $msg, $location);
			return;
		}
		
		if($exception === NULL) {
			$exceptionString = '';
		} else {
			$this->ringBuffer->write($this);
			$exceptionString = "\n" . $exception->__toString();
		}
		$pseudoThread = $_SERVER['REMOTE_ADDR'] . ':' . $_SERVER['REQUEST_TIME'] .  ':' . $_SERVER['REMOTE_PORT'];
		$str = sprintf("%-5s %s [%s] %s --- %s%s\n",
			$lvl->getStringValue(),
			date("Y-m-d h:i:s", time()), // u ... microseconds; not available on web.student.tuwien.ac.at
			$pseudoThread,
			$location->getFormattedString(),
			$msg, $exceptionString);
		# echo $str;
		
		if(fwrite($this->fh, $str) === false) {
			throw new Exception('Could not write log message!');
		}
	}
	
	public function closeSaveIfNecessary() {
		try {
			$this->closeIfNecessary();
		} catch(Exception $e) {
			// what to do with that one?!
			if(false) $e; // Zend Studio warning hack
		}
	}
	public function closeIfNecessary() {
		if($this->fh === NULL) {
			return;
		}
		$this->close();
	}
	
	public function close() {
		if($this->fh === NULL) {
			throw new Exception("Illegal state: Log not yet opened!");
		}
		if(fclose($this->fh) === false) {
			$this->fh = NULL;
			throw new Exception('Could not close log filehandle!');
		}
		$this->fh = NULL;
	}
	
}


define('_RINGBUFFER_SIZE', 2);
class RingBuffer {
	
	private $data = array();
	
	public function add(LogLevel $lvl, $msg, LogLocation $location) {
		$this->data[] = array(
			'lvl' => $lvl,
			'msg' => $msg,
			'location' => $location
		);
	}
	
	public function write(Log $log) {
		$start = 0;
		$dataSize = sizeof($this->data);
		if($dataSize > _RINGBUFFER_SIZE) {
			$start = $dataSize - _RINGBUFFER_SIZE;
			$n = _RINGBUFFER_SIZE + $start;
		} else {
			$n = $dataSize;
		}
		
		for($i = $start; $i < $n; $i++) {
			$arr = $this->data[$i];
			$log->writeFile($arr['lvl'], $arr['msg'], null, $arr['location']);
		}
		$this->data = array();
	}
	
}

class LogLocation {
	
	private $formattedString;
	
	public function LogLocation() {
		$backtrace = debug_backtrace();
		
		$invoker = NULL;
		foreach($backtrace as $trace) {
			$path = $trace['file'];
			$f = substr($path, strrpos($path, DIRECTORY_SEPARATOR) + 1, strlen($path));
			if($f != 'Log.class.php') {
				$invoker = $trace;
				break;
			}
		}
		$s = 'N/A';
		if($invoker !== NULL) {
			$basePath = '';
			$file = substr($invoker['file'], strlen($basePath), strlen($invoker['file']));
			$s = $file . ':' . $invoker['line'];
		}
		$this->formattedString = $s;
	}
	
	
	public function getFormattedString() {
		return $this->formattedString;
	}

}

?>